﻿using System;
using System.Net;
using System.Windows.Controls;
using Vertigo.SlideShow.Controls;
using Vertigo.SlideShow.Transitions;

namespace Vertigo.SlideShow
{
	/// <summary>
	/// Preloads slides into cache.
	/// </summary>
	public class DataPreloader
	{
		public bool PreloadAll { get; private set; }
		public bool PreloadCurrentAlbum { get; private set; }
		public int PreloadAhead { get; private set; }
		public static DownloadProgressIndicator ProgressIndicator { get; private set; }

		// State -1 indicates that the slideshow is not waiting for a slide to load
		private int toSlideIndex = -1;

		/// <summary>
		/// Initializes a new instance of the <see cref="DataPreloader"/> class.
		/// </summary>
		public DataPreloader()
		{
			PreloadAhead = Configuration.Options.Preloader.PreloadAhead;
		}

		/// <summary>
		/// Preloads slides based on configuration settings passed to the constructor.
		/// </summary>
		public void PreloadSlides()
		{
			AlbumHandler album = DataHandler.Albums[DataHandler.CurrentAlbumIndex];
			int toSlideIndex = album.TransitionManager.ToSlideIndex;

			for (int i = 0; i <= PreloadAhead; i++)
			{
				int slideIndex = TransitionManager.Mod(toSlideIndex + i, DataHandler.Albums[DataHandler.CurrentAlbumIndex].Slides.Length);
				if (!album.Slides[slideIndex].IsPreloaded)
				{
					Preload(album.Slides[slideIndex]);
				}
			}
		}

		/// <summary>
		/// Preloads the specified slide.
		/// </summary>
		/// <param name="slide">The slide.</param>
		private void Preload(SlideHandler slide)
		{
			PreloadClient client = new PreloadClient();
			client.PreloadProgressChanged += client_PreloadProgressChanged;
			client.PreloadCompleted += client_PreloadCompleted;
			client.Preload(slide);
		}

		/// <summary>
		/// Handles the PreloadProgressChanged event of the client control and updates the progress indicator.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="Vertigo.SlideShow.Preloader.SlidePreloadProgressChangedEventArgs"/> instance containing the event data.</param>
		private void client_PreloadProgressChanged(object sender, SlidePreloadProgressChangedEventArgs e)
		{
			if (!e.PreloadSlide.IsPreloaded && toSlideIndex != -1)
			{
				if (e.PreloadSlide == DataHandler.Albums[DataHandler.CurrentAlbumIndex].Slides[toSlideIndex] && ProgressIndicator != null)
				{
					ProgressIndicator.UpdateProgress(e.Percent);
				}
			}
		}

		/// <summary>
		/// Handles the PreloadCompleted event of the client control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="Vertigo.SlideShow.Preloader.SlidePreloadCompletedEventArgs"/> instance containing the event data.</param>
		private void client_PreloadCompleted(object sender, SlidePreloadCompletedEventArgs e)
		{
			if (!e.PreloadSlide.IsPreloaded)
			{
				if (toSlideIndex != -1)
				{
					if (e.PreloadSlide == DataHandler.Albums[DataHandler.CurrentAlbumIndex].Slides[toSlideIndex])
					{
						toSlideIndex = -1;
					}
				}

				e.PreloadSlide.TriggerToSlideFinishedLoadingEvent();
				e.PreloadSlide.IsPreloaded = true;
			}
		}

		/// <summary>
		/// Preloads the slide specified in the constructor. Supplies PreloadCompleted and PreloadProgressChanged events.
		/// </summary>
		public class PreloadClient
		{
			public SlideHandler PreloadSlide { get; private set; }
			public event PreloadProgressChangedHandler PreloadProgressChanged;
			public event PreloadCompletedHandler PreloadCompleted;

			public void Preload(SlideHandler slide)
			{
				PreloadSlide = slide;

				WebClient client = new WebClient();
				client.DownloadProgressChanged += client_DownloadProgressChanged;
				client.DownloadStringCompleted += client_DownloadStringCompleted;
				client.DownloadStringAsync(slide.Source);
			}

			private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
			{
				PreloadProgressChanged(this, new SlidePreloadProgressChangedEventArgs(e.ProgressPercentage, PreloadSlide));
			}

			private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
			{
				PreloadCompleted(this, new SlidePreloadCompletedEventArgs(e.Error, PreloadSlide));
			}
		}

		public delegate void PreloadProgressChangedHandler(object sender, SlidePreloadProgressChangedEventArgs e);

		/// <summary>
		/// Event arguments passed when a change in download progress has occured.
		/// </summary>
		public class SlidePreloadProgressChangedEventArgs : EventArgs
		{
			public int Percent { get; private set; }
			public SlideHandler PreloadSlide { get; private set; }

			public SlidePreloadProgressChangedEventArgs(int percent, SlideHandler preloadSlide)
			{
				Percent = percent;
				PreloadSlide = preloadSlide;
			}
		}

		public delegate void PreloadCompletedHandler(object sender, SlidePreloadCompletedEventArgs e);

		/// <summary>
		/// Event arguments passed when a download has finished.
		/// </summary>
		public class SlidePreloadCompletedEventArgs : EventArgs
		{
			public Exception Error { get; private set; }
			public SlideHandler PreloadSlide { get; private set; }
			public SlidePreloadCompletedEventArgs(Exception error, SlideHandler preloadSlide)
			{
				Error = error;
				PreloadSlide = preloadSlide;
			}
		}

		/// <summary>
		/// Displays the progress indicator.
		/// </summary>
		/// <param name="index">The index of the slide the indicator is to track.</param>
		public void DisplayProgressIndicator(int index)
		{
			toSlideIndex = index;

			// Look in configuration for indicator type, else default to bar progress indicator
			ProgressIndicator = DownloadProgressIndicator.Create(
				Configuration.Options.LoadingProgressIndicator.Type);

			DataHandler.Albums[DataHandler.CurrentAlbumIndex].TransitionManager.MediaRoot.Children.Add(ProgressIndicator);
		}

		/// <summary>
		/// Hides the progress indicator.
		/// </summary>
		public void HideProgressIndicator()
		{
			UIElementCollection children = DataHandler.Albums[DataHandler.CurrentAlbumIndex].TransitionManager.MediaRoot.Children;
			if (children.Contains(ProgressIndicator))
			{
				children.Remove(ProgressIndicator);
			}
		}
	}
}